/*
* Copyright (c) 2025 Huawei Technologies Co., Ltd.
* openUBMC is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*         http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/

#include <gtest/gtest.h>
#include <test_utilities/test_base.h>
#include "interface/pcie_device.h"
#include <ncsi_over_mctp/ncsi_over_mctp_mellanox.h>
#include <mctp/mctp.h>
#include <mc/engine.h>
#include <mc/common.h>
#include <mc/variant.h>
#include <ncsi/ncsi_protocol.h>
#include <ncsi/ncsi_socket.h>
#include "mock_ipmi_core.h"
#include <ctime>
#include <chrono>
#include <thread>
#include <functional>
#include <vector>
#include <string>

// 声明外部 MCTP 请求处理器设置函数，具体实现在 mock_mctp.cpp 中
namespace dev {
void set_mctp_request_handler(std::function<bool(const mc::dict&, const std::vector<uint8_t>&,
                                                 std::string&)> handler);
}

namespace drivers {
namespace pcie_nic_card {
namespace mellanox {
namespace interface {
namespace test {

namespace {
// 字节序转换辅助函数
constexpr uint16_t host_to_be16(uint16_t value) {
    return static_cast<uint16_t>(((value & 0x00FFU) << 8U) | ((value & 0xFF00U) >> 8U));
}
} // namespace

class pcie_device_test_service : public mc::engine::service {
public:
    pcie_device_test_service(const std::string& name) : mc::engine::service(name) {}
};

// Mock ncsi_over_mctp_mellanox 类
class mock_ncsi_over_mctp_mellanox : public dev::ncsi_over_mctp_mellanox {
public:
    mock_ncsi_over_mctp_mellanox(dev::mctp& mctp_obj) : dev::ncsi_over_mctp_mellanox(mctp_obj) {
    }

    void set_get_version_id_result(bool result, dev::ncsi_version_id_info_t version_id = {}) {
        m_get_version_id_result = result;
        m_version_id            = version_id;
    }

    bool get_version_id(uint8_t package_id, uint8_t channel_id,
                        dev::ncsi_version_id_info_t* version_id) {
        if (version_id == nullptr) {
            return false;
        }
        if (!m_get_version_id_result) {
            return false;
        }
        *version_id = m_version_id;
        return true;
    }

private:
    bool                        m_get_version_id_result = false;
    dev::ncsi_version_id_info_t m_version_id            = {};
};

// ========================= PCIeDevice测试 =========================
class PCIeDeviceTest : public mc::test::TestWithEngine {
protected:
    static void SetUpTestSuite() {
        TestWithEngine::SetUpTestSuite();
        // 使用与 IPMB 库期望的服务名一致的服务名，避免超时
        // IPMB 库查找的服务名是 "bmc.kepler.ipmi_core"（定义在 libraries/ipmb/ipmb.h）
        m_test_service = std::make_unique<pcie_device_test_service>("bmc.openubmc.test_pcie_device");
        m_test_service->init();
        m_test_service->start();
        
        // 注册 mock IpmiCore 接口，避免真实的 D-Bus 调用
        m_ipmi_core_obj = test_ipmi_core_object::create();
        m_ipmi_core_obj->set_object_path("/bmc/kepler/IpmiCore");
        m_ipmi_core_obj->set_object_name("bmc.kepler.IpmiCore");
        m_test_service->register_object(m_ipmi_core_obj);
    }
    static void TearDownTestSuite() {
        try {
            if (m_ipmi_core_obj && m_test_service) {
                m_test_service->unregister_object(m_ipmi_core_obj);
                m_ipmi_core_obj.reset();
            }
            if (m_test_service) {
                m_test_service->stop();
                m_test_service.reset();
            }
            TestWithEngine::TearDownTestSuite();
        } catch (...) {
            if (m_ipmi_core_obj) {
                m_ipmi_core_obj.reset();
            }
            if (m_test_service) {
                m_test_service.reset();
            }
        }
    }
    void SetUp() override {
        static int test_counter = 0;
        test_counter++;
        std::string object_name =
            "test_pcie_device_" + std::to_string(test_counter) + "_" + std::to_string(std::time(nullptr));
        std::string position = std::to_string(test_counter + 4000);

        m_pcie_device = mc::make_shared<dev::PCIeDevice>();

        // 创建 mock ncsi_over_mctp_mellanox
        m_mctp = mc::make_shared<dev::mctp>(nullptr, 0, dev::MCTP_MESSAGE_TYPE_NCSI, "");
        m_mock_ncsi = std::make_shared<mock_ncsi_over_mctp_mellanox>(*m_mctp);
    }
    void TearDown() override {
        try {
            if (m_pcie_device) {
                m_pcie_device->stop_ncsi_update_task();
                // 等待所有异步操作完成（如 post 回调），避免在服务停止时回调仍在队列中
                std::this_thread::sleep_for(std::chrono::milliseconds(20));
                m_pcie_device.reset();
            }
            // 清理 MCTP 请求处理器，避免影响其他测试
            dev::set_mctp_request_handler(nullptr);
            m_mock_ncsi.reset();
            m_mctp.reset();
        } catch (...) {
            if (m_pcie_device) {
                m_pcie_device.reset();
            }
        }
    }

    static std::unique_ptr<pcie_device_test_service> m_test_service;
    static mc::shared_ptr<test_ipmi_core_object> m_ipmi_core_obj;
    mc::shared_ptr<dev::PCIeDevice>                  m_pcie_device;
    mc::shared_ptr<dev::mctp>                        m_mctp;
    std::shared_ptr<mock_ncsi_over_mctp_mellanox>    m_mock_ncsi;
};
std::unique_ptr<pcie_device_test_service> PCIeDeviceTest::m_test_service;
mc::shared_ptr<test_ipmi_core_object> PCIeDeviceTest::m_ipmi_core_obj;

// ========================= 基本功能测试 =========================

TEST_F(PCIeDeviceTest, Constructor) {
    dev::PCIeDevice pcie_device;
    EXPECT_TRUE(true);
}

TEST_F(PCIeDeviceTest, Destructor_CleansUpResources) {
    auto pcie_device = mc::make_shared<dev::PCIeDevice>();

    // 启动任务后析构
    mc::milliseconds interval(1000);
    uint8_t          package_id = 0;
    pcie_device->start_ncsi_update_task_mellanox(m_mock_ncsi, interval, package_id);
    // 由于使用了 mock 接口，无需等待真实的 D-Bus 调用完成
    pcie_device->stop_ncsi_update_task();
    pcie_device.reset();

    EXPECT_TRUE(pcie_device == nullptr);
}

// ========================= SetRootBDF 测试 =========================

TEST_F(PCIeDeviceTest, SetRootBDF_Success) {
    uint8_t bus      = 1;
    uint8_t device   = 2;
    uint8_t function = 3;

    bool result = m_pcie_device->SetRootBDF(bus, device, function);
    EXPECT_TRUE(result);
    EXPECT_EQ(m_pcie_device->Bus.get_value(), bus);
    EXPECT_EQ(m_pcie_device->Device.get_value(), device);
    EXPECT_EQ(m_pcie_device->Function.get_value(), function);
}

TEST_F(PCIeDeviceTest, SetRootBDF_InvalidBDF) {
    // bus == 0 && device == 0 && function == 0 应该返回 false
    bool result = m_pcie_device->SetRootBDF(0, 0, 0);
    EXPECT_FALSE(result);
}

TEST_F(PCIeDeviceTest, SetRootBDF_BoundaryValues) {
    // 测试边界值
    std::vector<std::tuple<uint8_t, uint8_t, uint8_t>> valid_bdfs = {
        {1, 0, 0}, {0, 1, 0}, {0, 0, 1}, {255, 255, 255}, {1, 1, 1}};

    for (auto& [bus, device, function] : valid_bdfs) {
        bool result = m_pcie_device->SetRootBDF(bus, device, function);
        EXPECT_TRUE(result);
        EXPECT_EQ(m_pcie_device->Bus.get_value(), bus);
        EXPECT_EQ(m_pcie_device->Device.get_value(), device);
        EXPECT_EQ(m_pcie_device->Function.get_value(), function);
    }
}

// ========================= SetDeviceBDF 测试 =========================

TEST_F(PCIeDeviceTest, SetDeviceBDF_Success) {
    uint8_t bus      = 4;
    uint8_t device   = 5;
    uint8_t function = 6;

    bool result = m_pcie_device->SetDeviceBDF(bus, device, function);
    EXPECT_TRUE(result);
    EXPECT_EQ(m_pcie_device->DevBus.get_value(), bus);
    EXPECT_EQ(m_pcie_device->DevDevice.get_value(), device);
    EXPECT_EQ(m_pcie_device->DevFunction.get_value(), function);
}

TEST_F(PCIeDeviceTest, SetDeviceBDF_InvalidBDF) {
    // bus == 0 && device == 0 && function == 0 应该返回 false
    bool result = m_pcie_device->SetDeviceBDF(0, 0, 0);
    EXPECT_FALSE(result);
}

TEST_F(PCIeDeviceTest, SetDeviceBDF_SignalEmitted) {
    uint8_t bus      = 7;
    uint8_t device   = 8;
    uint8_t function = 9;

    bool signal_received = false;
    uint8_t received_bus, received_device, received_function;

    m_pcie_device->pcie_device_bdf_changed.connect([&](uint8_t b, uint8_t d, uint8_t f) {
        signal_received      = true;
        received_bus         = b;
        received_device      = d;
        received_function    = f;
    });

    bool result = m_pcie_device->SetDeviceBDF(bus, device, function);
    EXPECT_TRUE(result);

    // post 方法是异步的，需要等待信号被触发
    std::this_thread::sleep_for(std::chrono::milliseconds(10));
    EXPECT_TRUE(signal_received);
    EXPECT_EQ(received_bus, bus);
    EXPECT_EQ(received_device, device);
    EXPECT_EQ(received_function, function);
}

// ========================= update_pcie_device_id_by_ncsi 测试 =========================

TEST_F(PCIeDeviceTest, UpdatePcieDeviceIdByNcsi_Success) {
    dev::ncsi_version_id_info_t version_id = {};
    version_id.vid                          = "0x1981";
    version_id.did                          = "0x1015";
    version_id.svid                         = "0x1981";
    version_id.sdid                         = "0x1015";

    // 设置 MCTP 请求处理器，模拟 GET_VERSION_ID 成功场景
    dev::set_mctp_request_handler([version_id](const mc::dict& ctx, const std::vector<uint8_t>&,
                                               std::string& rsp) {
        EXPECT_EQ(ctx.at("PacketType").as<int64_t>(), GET_VERSION_ID);
        get_version_id_rsp response{};
        response.rsp_code    = host_to_be16(COMMAND_COMPLETED);
        response.reason_code = host_to_be16(NOERROR_OR_NOREASON);
        // 固件版本在该测试中不关心，填充为 0
        response.firmware_ver[0] = 0;
        response.firmware_ver[1] = 0;
        response.firmware_ver[2] = 0;
        response.firmware_ver[3] = 0;
        // 将字符串形式的 PCI ID 转换为数值并填充到响应中
        uint16_t vid  = static_cast<uint16_t>(std::stoi(version_id.vid, nullptr, 16));
        uint16_t did  = static_cast<uint16_t>(std::stoi(version_id.did, nullptr, 16));
        uint16_t svid = static_cast<uint16_t>(std::stoi(version_id.svid, nullptr, 16));
        uint16_t sdid = static_cast<uint16_t>(std::stoi(version_id.sdid, nullptr, 16));
        response.pci_vid  = host_to_be16(vid);
        response.pci_did  = host_to_be16(did);
        response.pci_ssid = host_to_be16(sdid);
        response.pci_svid = host_to_be16(svid);
        rsp = std::string(reinterpret_cast<const char*>(&response), GET_VERSION_ID_RSP_LEN);
        return true;
    });

    // 需要先设置 ncsi_over_mctp_mellanox
    mc::milliseconds interval(1000);
    uint8_t          package_id = 0;
    m_pcie_device->start_ncsi_update_task_mellanox(m_mock_ncsi, interval, package_id);
    // 由于使用了 mock 接口，无需等待真实的 D-Bus 调用完成
    bool result = m_pcie_device->update_pcie_device_id_by_ncsi();
    EXPECT_TRUE(result);
    EXPECT_EQ(m_pcie_device->VendorId.get_value(), "0x1981");
    EXPECT_EQ(m_pcie_device->DeviceId.get_value(), "0x1015");
    EXPECT_EQ(m_pcie_device->SubSystemVendorId.get_value(), "0x1981");
    EXPECT_EQ(m_pcie_device->SubSystemDeviceId.get_value(), "0x1015");
}

TEST_F(PCIeDeviceTest, UpdatePcieDeviceIdByNcsi_GetVersionIdFailed) {
    // 设置 MCTP 请求处理器，模拟 GET_VERSION_ID 失败场景
    dev::set_mctp_request_handler([](const mc::dict&, const std::vector<uint8_t>&, std::string&) {
        return false;
    });

    mc::milliseconds interval(1000);
    uint8_t          package_id = 0;
    m_pcie_device->start_ncsi_update_task_mellanox(m_mock_ncsi, interval, package_id);
    // 由于使用了 mock 接口，无需等待真实的 D-Bus 调用完成
    bool result = m_pcie_device->update_pcie_device_id_by_ncsi();
    EXPECT_FALSE(result);
}

TEST_F(PCIeDeviceTest, UpdatePcieDeviceIdByNcsi_ExistingValuesNotOverwritten) {
    // 设置已有值
    m_pcie_device->VendorId.set_value("0x1234");
    m_pcie_device->DeviceId.set_value("0x5678");

    dev::ncsi_version_id_info_t version_id = {};
    version_id.vid                          = "0x1981";
    version_id.did                          = "0x1015";
    version_id.svid                         = "0x1981";
    version_id.sdid                         = "0x1015";

    // 设置 MCTP 请求处理器，模拟 GET_VERSION_ID 成功场景（与 Success 用例一致）
    dev::set_mctp_request_handler([version_id](const mc::dict& ctx, const std::vector<uint8_t>&,
                                               std::string& rsp) {
        EXPECT_EQ(ctx.at("PacketType").as<int64_t>(), GET_VERSION_ID);
        get_version_id_rsp response{};
        response.rsp_code    = host_to_be16(COMMAND_COMPLETED);
        response.reason_code = host_to_be16(NOERROR_OR_NOREASON);
        response.firmware_ver[0] = 0;
        response.firmware_ver[1] = 0;
        response.firmware_ver[2] = 0;
        response.firmware_ver[3] = 0;
        uint16_t vid  = static_cast<uint16_t>(std::stoi(version_id.vid, nullptr, 16));
        uint16_t did  = static_cast<uint16_t>(std::stoi(version_id.did, nullptr, 16));
        uint16_t svid = static_cast<uint16_t>(std::stoi(version_id.svid, nullptr, 16));
        uint16_t sdid = static_cast<uint16_t>(std::stoi(version_id.sdid, nullptr, 16));
        response.pci_vid  = host_to_be16(vid);
        response.pci_did  = host_to_be16(did);
        response.pci_ssid = host_to_be16(sdid);
        response.pci_svid = host_to_be16(svid);
        rsp = std::string(reinterpret_cast<const char*>(&response), GET_VERSION_ID_RSP_LEN);
        return true;
    });

    mc::milliseconds interval(1000);
    uint8_t          package_id = 0;
    m_pcie_device->start_ncsi_update_task_mellanox(m_mock_ncsi, interval, package_id);
    // 由于使用了 mock 接口，无需等待真实的 D-Bus 调用完成
    bool result = m_pcie_device->update_pcie_device_id_by_ncsi();
    EXPECT_TRUE(result);
    // 已有值不应该被覆盖
    EXPECT_EQ(m_pcie_device->VendorId.get_value(), "0x1234");
    EXPECT_EQ(m_pcie_device->DeviceId.get_value(), "0x5678");
    // 空值应该被设置
    EXPECT_EQ(m_pcie_device->SubSystemVendorId.get_value(), "0x1981");
    EXPECT_EQ(m_pcie_device->SubSystemDeviceId.get_value(), "0x1015");
}

TEST_F(PCIeDeviceTest, UpdatePcieDeviceIdByNcsi_WithoutNcsiInitialized) {
    // 不启动任务，直接调用 update_pcie_device_id_by_ncsi
    bool result = m_pcie_device->update_pcie_device_id_by_ncsi();
    // 由于 m_ncsi_over_mctp_mellanox 为 nullptr，应该会失败
    EXPECT_FALSE(result);
}

// ========================= replace_dynamic_property 测试 =========================

TEST_F(PCIeDeviceTest, ReplaceDynamicProperty_WithSlot) {
    mc::mutable_dict object;
    mc::mutable_dict pcie_device_obj;
    pcie_device_obj["Slot"] = "${Slot}";
    object["bmc.dev.PCIeDevice"] = pcie_device_obj;

    mc::dict connector;
    connector["Slot"] = static_cast<uint8_t>(3);

    m_pcie_device->replace_dynamic_property(object, connector);

    mc::mutable_dict updated_pcie_device_obj = object["bmc.dev.PCIeDevice"].as<mc::mutable_dict>();
    EXPECT_EQ(updated_pcie_device_obj["Slot"].as<uint8_t>(), 3);
}

TEST_F(PCIeDeviceTest, ReplaceDynamicProperty_WithDeviceName) {
    mc::mutable_dict object;
    mc::mutable_dict pcie_device_obj;
    pcie_device_obj["DeviceName"] = "Device_${Slot}";
    object["bmc.dev.PCIeDevice"] = pcie_device_obj;

    mc::dict connector;
    connector["Slot"] = static_cast<uint8_t>(5);

    m_pcie_device->replace_dynamic_property(object, connector);

    mc::mutable_dict updated_pcie_device_obj = object["bmc.dev.PCIeDevice"].as<mc::mutable_dict>();
    std::string device_name = updated_pcie_device_obj["DeviceName"].as<std::string>();
    EXPECT_TRUE(device_name.find("5") != std::string::npos);
}

TEST_F(PCIeDeviceTest, ReplaceDynamicProperty_WithBothSlotAndDeviceName) {
    mc::mutable_dict object;
    mc::mutable_dict pcie_device_obj;
    pcie_device_obj["Slot"]       = "${Slot}";
    pcie_device_obj["DeviceName"] = "Device_${Slot}";
    object["bmc.dev.PCIeDevice"] = pcie_device_obj;

    mc::dict connector;
    connector["Slot"] = static_cast<uint8_t>(7);

    m_pcie_device->replace_dynamic_property(object, connector);

    mc::mutable_dict updated_pcie_device_obj = object["bmc.dev.PCIeDevice"].as<mc::mutable_dict>();
    EXPECT_EQ(updated_pcie_device_obj["Slot"].as<uint8_t>(), 7);
    std::string device_name = updated_pcie_device_obj["DeviceName"].as<std::string>();
    EXPECT_TRUE(device_name.find("7") != std::string::npos);
}

TEST_F(PCIeDeviceTest, ReplaceDynamicProperty_WithoutPlaceholders) {
    mc::mutable_dict object;
    mc::mutable_dict pcie_device_obj;
    pcie_device_obj["Slot"]       = static_cast<uint8_t>(10);
    pcie_device_obj["DeviceName"] = "FixedName";
    object["bmc.dev.PCIeDevice"] = pcie_device_obj;

    mc::dict connector;
    connector["Slot"] = static_cast<uint8_t>(5);

    m_pcie_device->replace_dynamic_property(object, connector);

    mc::mutable_dict updated_pcie_device_obj = object["bmc.dev.PCIeDevice"].as<mc::mutable_dict>();
    // 非占位符值应该保持不变
    EXPECT_EQ(updated_pcie_device_obj["Slot"].as<uint8_t>(), 10);
    EXPECT_EQ(updated_pcie_device_obj["DeviceName"].as<std::string>(), "FixedName");
}

// ========================= start_ncsi_update_task_mellanox 测试 =========================

TEST_F(PCIeDeviceTest, StartNcsiUpdateTask_Success) {
    mc::milliseconds interval(1000);
    uint8_t          package_id = 0;

    m_pcie_device->start_ncsi_update_task_mellanox(m_mock_ncsi, interval, package_id);
    // 由于使用了 mock 接口，无需等待真实的 D-Bus 调用完成
    // 验证任务已启动，可以通过停止来验证
    m_pcie_device->stop_ncsi_update_task();
    EXPECT_TRUE(true);
}

TEST_F(PCIeDeviceTest, StartNcsiUpdateTask_WithNullNcsi) {
    mc::milliseconds              interval(1000);
    uint8_t                       package_id = 0;
    dev::ncsi_over_mctp_mellanox_ptr null_ncsi = nullptr;

    // 应该能正常处理空指针
    m_pcie_device->start_ncsi_update_task_mellanox(null_ncsi, interval, package_id);
    // 由于使用了 mock 接口，无需等待真实的 D-Bus 调用完成
    m_pcie_device->stop_ncsi_update_task();
    EXPECT_TRUE(true);
}

// ========================= stop_ncsi_update_task 测试 =========================

TEST_F(PCIeDeviceTest, StopNcsiUpdateTask_WithoutStart) {
    // 未启动时停止应该能正常处理
    m_pcie_device->stop_ncsi_update_task();
    EXPECT_TRUE(true);
}

TEST_F(PCIeDeviceTest, StopNcsiUpdateTask_AfterStart) {
    mc::milliseconds interval(1000);
    uint8_t          package_id = 0;

    m_pcie_device->start_ncsi_update_task_mellanox(m_mock_ncsi, interval, package_id);
    // 由于使用了 mock 接口，无需等待真实的 D-Bus 调用完成
    m_pcie_device->stop_ncsi_update_task();
    EXPECT_TRUE(true);
}

// ========================= 定时器回调测试 =========================

TEST_F(PCIeDeviceTest, TimerCallback_UpdatesDeviceId) {
    dev::ncsi_version_id_info_t version_id = {};
    version_id.vid                          = "0x1981";
    version_id.did                          = "0x1015";
    version_id.svid                         = "0x1981";
    version_id.sdid                         = "0x1015";
    m_mock_ncsi->set_get_version_id_result(true, version_id);

    mc::milliseconds interval(100);
    uint8_t          package_id = 0;
    m_pcie_device->start_ncsi_update_task_mellanox(m_mock_ncsi, interval, package_id);
    // 由于使用了 mock 接口，无需等待定时器触发
    // 验证设备ID是否被更新（由于定时器回调会调用 update_pcie_device_id_by_ncsi）
    // 注意：由于是异步操作，这里主要验证不会崩溃
    m_pcie_device->stop_ncsi_update_task();
    EXPECT_TRUE(true);
}

// ========================= 多实例测试 =========================

TEST_F(PCIeDeviceTest, MultiInstance) {
    std::vector<mc::shared_ptr<dev::PCIeDevice>> pcie_devices;
    for (int i = 0; i < 3; i++) {
        std::string object_name =
            "pcie_device_" + std::to_string(i) + "_" + std::to_string(std::time(nullptr)) + "_" +
            std::to_string(i * 1000);
        std::string position = std::to_string(i + 9000);

        auto pcie_device = mc::make_shared<dev::PCIeDevice>();

        mc::milliseconds interval(1000);
        uint8_t          package_id = i;
        pcie_device->start_ncsi_update_task_mellanox(m_mock_ncsi, interval, package_id);
        pcie_devices.push_back(pcie_device);
    }

    // 由于使用了 mock 接口，无需等待真实的 D-Bus 调用完成
    for (auto& pcie_device : pcie_devices) {
        if (pcie_device && m_test_service) {
            try {
                pcie_device->stop_ncsi_update_task();
            } catch (...) {
            }
        }
    }
    pcie_devices.clear();
    EXPECT_TRUE(true);
}

} // namespace test
} // namespace interface
} // namespace mellanox
} // namespace pcie_nic_card
} // namespace drivers


